﻿using System;
using System.Collections.Generic;
using System.Linq;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental;
using TSU.FAMC.ComputerEngineering.Classes.Enums;
using System.IO;
using TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Functions.Readers;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees.Basis;
using TSU.FAMC.ComputerEngineering.Classes.OrandTrees.Readers;

namespace TSU.FAMC.ComputerEngineering.Classes.Diagnostics.PathDelayFaults
{
    /// <summary>
    /// Encapsulates function for analyzing PDF faults of boolean systems.
    /// </summary>
    public class PdfAnalyzer
    {
        // Properties

        /// <summary>
        /// Test for robust path-delay faults.
        /// </summary>
        public List<PdfTest> RobustTest;

        /// <summary>
        /// Test for non-robust path-delay faults.
        /// </summary>
        public List<PdfTest> NonRobustTest;

        /// <summary>
        /// Test for non-robust path-delay faults, which are relatively robust testable (intersects only with robust).
        /// </summary>
        public List<PdfTest> NonRobustRelativelyRobusTest;


        // Methods

        /// <summary>
        /// Default constructor.
        /// </summary>
        public PdfAnalyzer()
        {
            RobustTest = new List<PdfTest>();
            NonRobustTest = new List<PdfTest>();
            NonRobustRelativelyRobusTest = new List<PdfTest>();
        }

        /// <summary>
        /// Returns test for robust and non-robust path-delay faults of the circuit.
        /// </summary>
        /// <param name="circuit">Circuit for analysis.</param>
        /// <returns>List of PDF tests.</returns>
        public void BuildTest(Circuits.Circuit circuit)
        {
            RobustTest.Clear();
            NonRobustTest.Clear();
            NonRobustRelativelyRobusTest.Clear();
            
            // buidling ORAND-tree for the circuit
            var builder = new OrandTreeReaderCircuit {Input = circuit};
            builder.Read();

            // building system
            var systemBuilder = new BooleanSystemReaderOrandTree {System = new BooleanSystem(), Input = builder.Tree};
            systemBuilder.Read();

            var tree = builder.Tree;

            // analysing every path (leaf of the tree)

            // retreiving all conjunctions

            // iterating through functions
            for (var fn = 0; fn < tree.Tops.Count; fn++)
            {
                // building fictive tree with one top to iterate conjunctions
                var fTree = new OrandTree();
                fTree.Tops.Add(tree.Tops[fn]);

                // copying leaves
                foreach (var leaf in tree.Leaves)
                {
                    fTree.Leaves.Add(leaf);
                }

                var sop = (from object conjunction in fTree select conjunction as ElementInterface).ToList();


                // dictionary for memorizing non-robust paths
                var nonRobustPaths = new Dictionary<Vertex, List<ElementInterface>>();

                // iterating paths
                foreach (var leaf in fTree.Leaves)
                {
                    // retreiving input mark
                    var inputMark = OrandTree.GetInputMark(leaf);
                    var inputNumber = Int32.Parse(inputMark);


                    // building set of conjunctions which contains current path (leaf)
                    var kSet = sop.Where(conjunction => conjunction.Any(vertex => ReferenceEquals(vertex.RelatedElement, leaf))).ToList();
                    if (kSet.Count == 0)
                    {
                        // leaf does not belong to current tree
                        continue;
                        //throw new InvalidDataException("Could not build K set.");
                    }

                    // building SOP without K
                    var sopWithoutK = sop.Where(kw => !kSet.Contains(kw)).ToList();


                    // STEP 1.
                    // trying build robust test for selected path using conjunctions from K sequentially

                    var robustTestFound = false;
                    foreach (var k in kSet)
                    {
                        // building k without path variable
                        var kWithoutPath = new ElementInterface();
                        var pathVertex = new Vertex();
                        foreach (var v in k)
                        {
                            if (ReferenceEquals(v.RelatedElement, leaf))
                            {
                                pathVertex = (Vertex)v;
                                continue;
                            }

                            kWithoutPath.Add(v);
                        }

                        // building cube representing kWithoutPath
                        var kWithoutPathCube = new Cube(circuit.Inputs.Count);
                        for (var i = 0; i < kWithoutPathCube.Dimension; i++)
                        {
                            kWithoutPathCube[i] = BooleanValue.Undefined;
                        }

                        foreach (Vertex v in kWithoutPath)
                        {
                            var numberStr = v.Data as String;
                            if (numberStr == null) continue;

                            var number = Int32.Parse(numberStr);
                            kWithoutPathCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                        }

                        // building SOP for equation to solve SOP = 0
                        var d = new CubeSet();
                        foreach (var conj in sopWithoutK)
                        {
                            // building cube representing conj
                            var conjCube = new Cube(circuit.Inputs.Count);
                            for (var i = 0; i < conjCube.Dimension; i++)
                            {
                                conjCube[i] = BooleanValue.Undefined;
                            }

                            foreach (Vertex v in conj)
                            {
                                var numberStr = v.Data as String;
                                if (numberStr == null) continue;

                                var number = Int32.Parse(numberStr);
                                conjCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                            }

                            // checking for orthogonality
                            if (kWithoutPathCube.IsOrthogonalTo(conjCube))
                            {
                                // skipping orthogonal cubes
                                continue;
                            }

                            d.Add(conjCube);
                        }

                        // deleting variables from D identical to kWithoutPathCube
                        foreach (var cube in d)
                        {
                            // reseting bit corresponding selected input
                            cube[inputNumber] = BooleanValue.Undefined;

                            for (var i = 0; i < kWithoutPathCube.Dimension; i++)
                            {
                                if (kWithoutPathCube[i] != cube[i])
                                {
                                    continue;
                                }

                                cube[i] = BooleanValue.Undefined;
                            }
                        }


                        // calculating the root of the equaion D = 0
                        var root = d.EquationNullRoot;

                        if (d.Count > 0 && root.Rank == 0)
                        {
                            // D == 1
                            continue;
                        }

                        if (root.Dimension == 0)
                        {
                            root = new Cube(kWithoutPathCube.Dimension);
                            for (var i = 0; i < root.Dimension; i++)
                            {
                                root[i] = BooleanValue.Undefined;
                            }
                        }

                        // building test pair
                        var v1 = new Cube(kWithoutPathCube);
                        for (var i = 0; i < v1.Dimension; i++)
                        {
                            if (root[i] == BooleanValue.Undefined)
                            {
                                continue;
                            }

                            v1[i] = root[i];
                        }
                        var v2 = new Cube(v1);

                        v1[inputNumber] = pathVertex.IsInverted == false ? BooleanValue.False : BooleanValue.True;
                        v2[inputNumber] = pathVertex.IsInverted == false ? BooleanValue.True : BooleanValue.False;

                        // checking vectors
                        var function = systemBuilder.System.Functions[fn];
                        if (function.Calculate(v1) == BooleanValue.True)
                        {
                            throw new Exception("Vector v1 must belongs to the Domain0 of the function.");
                        }

                        if (function.Calculate(v2) != BooleanValue.True)
                        {
                            throw new Exception("Vector v2 must belongs to the Domain1 of the function.");
                        }


                        // checking for robustness (no intersection with other conjunctions)
                        var vectorsInterval = v1.Closure(v2);
                        foreach (var conj in sopWithoutK)
                        {
                            // building cube representing conj
                            var conjCube = new Cube(circuit.Inputs.Count);
                            for (var i = 0; i < conjCube.Dimension; i++)
                            {
                                conjCube[i] = BooleanValue.Undefined;
                            }

                            foreach (Vertex v in conj)
                            {
                                var numberStr = v.Data as String;
                                if (numberStr == null) continue;

                                var number = Int32.Parse(numberStr);
                                conjCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                            }

                            // checking for orthogonality
                            if (!vectorsInterval.IsOrthogonalTo(conjCube))
                            {
                                // no one intersection allowed for robust test
                                throw new Exception("Robustness test failed for current test vectors pair.");
                            }
                        }


                        // building new PDF test
                        var test = new PdfTest {V1 = v1, V2 = v2, Data = fn, Data2 = fTree.GetPath(leaf)};
                        RobustTest.Add(test);

                        // going to the next path
                        robustTestFound = true;
                        break;
                    }


                    if (robustTestFound)
                    {
                        continue;
                    }

                    // memorizing current path as a leaf vertext and K set to build non-robust test after
                    nonRobustPaths.Add(leaf, kSet);
                }




                // STEP 2.
                // trying build test for non-robust path delay faults
                var nonRobustPathsCopy = new Dictionary<Vertex, List<ElementInterface>>(nonRobustPaths);
                foreach (var pair in nonRobustPaths)
                {
                    var leaf = pair.Key;
                    var kSet = pair.Value;

                    // retreiving input mark
                    var inputMark = OrandTree.GetInputMark(leaf);
                    var inputNumber = Int32.Parse(inputMark);


                    // iterating through K conjunctions
                    var testCandidates = new List<PdfTest>();
                    foreach (var ki in kSet)
                    {
                        // building cube representing k
                        var kCube = new Cube(circuit.Inputs.Count);
                        for (var i = 0; i < kCube.Dimension; i++)
                        {
                            kCube[i] = BooleanValue.Undefined;
                        }

                        foreach (Vertex v in ki)
                        {
                            var numberStr = v.Data as String;
                            if (numberStr == null) continue;

                            var number = Int32.Parse(numberStr);
                            kCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                        }

                        // building SOP without K for non-robust
                        var sopWithoutKn = sop.Where(k => !kSet.Contains(k)).ToList();


                        // building v1

                        // building SOP for equation to solve SOP = 0
                        var dd = new CubeSet();
                        foreach (var conj in sopWithoutKn)
                        {
                            // building cube representing conj
                            var conjCube = new Cube(circuit.Inputs.Count);
                            for (var i = 0; i < conjCube.Dimension; i++)
                            {
                                conjCube[i] = BooleanValue.Undefined;
                            }

                            foreach (Vertex v in conj)
                            {
                                var numberStr = v.Data as String;
                                if (numberStr == null) continue;

                                var number = Int32.Parse(numberStr);
                                conjCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                            }


                            // checking for orthogonality
                            if (kCube.IsOrthogonalTo(conjCube))
                            {
                                // skipping orthogonal cubes
                                continue;
                            }

                            dd.Add(conjCube);
                        }

                        // deleting variables from D identical (with identical sign) to kCube
                        foreach (var cube in dd)
                        {
                            for (var i = 0; i < kCube.Dimension; i++)
                            {
                                if (kCube[i] != cube[i])
                                {
                                    continue;
                                }

                                cube[i] = BooleanValue.Undefined;
                            }
                        }


                        // calculating the root of the equaion D = 0
                        var rootK = dd.EquationNullRoot;
                        if (rootK.Rank == 0)
                        {
                            // D == 1, something wrong
                            // trying next conjunction
                            continue;
                            //throw new InvalidDataException("D can not be equal to 1 for non-robust pdf!");
                        }

                        // determing v2
                        var v2N = new Cube(kCube);
                        for (var i = 0; i < v2N.Dimension; i++)
                        {
                            if (rootK[i] == BooleanValue.Undefined)
                            {
                                continue;
                            }

                            v2N[i] = rootK[i];
                        }




                        // building v1
                        var kCubeComplement = new Cube(kCube);
                        kCubeComplement[inputNumber] = kCubeComplement[inputNumber] == BooleanValue.True ? BooleanValue.False : BooleanValue.True;

                        
                        // building set of cubes for equation
                        var sopWithoutKi = sop.Where(k => !ReferenceEquals(ki, k)).ToList();


                        // building SOP for equation to solve SOP = 0
                        var dComplement = new CubeSet();
                        foreach (var conj in sopWithoutKi)
                        {
                            // building cube representing conj
                            var conjCube = new Cube(circuit.Inputs.Count);
                            for (var i = 0; i < conjCube.Dimension; i++)
                            {
                                conjCube[i] = BooleanValue.Undefined;
                            }

                            foreach (Vertex v in conj)
                            {
                                var numberStr = v.Data as String;
                                if (numberStr == null) continue;

                                var number = Int32.Parse(numberStr);
                                conjCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                            }

                            // checking for orthogonality
                            if (kCubeComplement.IsOrthogonalTo(conjCube))
                            {
                                // skipping orthogonal cubes
                                continue;
                            }

                            dComplement.Add(conjCube);
                        }


                        // deleting variables from D identical (with identical sign) to kCubeComplement
                        foreach (var cube in dComplement)
                        {
                            for (var i = 0; i < kCubeComplement.Dimension; i++)
                            {
                                if (kCubeComplement[i] != cube[i])
                                {
                                    continue;
                                }

                                cube[i] = BooleanValue.Undefined;
                            }
                        }

                        // calculating the root of the equaion D = 0
                        var rootComplement = dComplement.EquationNullRoot;
                        if (rootComplement.Rank == 0)
                        {
                            // D == 1, something wrong
                            // trying next conjunction
                            continue;
                            //throw new InvalidDataException("DComplement can not be equal to 1 for non-robust pdf!");
                        }

                        var v1N = new Cube(kCubeComplement);
                        for (var i = 0; i < v1N.Dimension; i++)
                        {
                            if (rootComplement[i] == BooleanValue.Undefined)
                            {
                                continue;
                            }

                            v1N[i] = rootComplement[i];
                        }


                        // checking vectors
                        var functionN = systemBuilder.System.Functions[fn];
                        if (functionN.Calculate(v1N) == BooleanValue.True)
                        {
                            throw new Exception("Vector v1 must belongs to the Domain0 of the function.");
                        }

                        if (functionN.Calculate(v2N) != BooleanValue.True)
                        {
                            throw new Exception("Vector v2 must belongs to the Domain1 of the function.");
                        }

                        // distance must be >= 2
                        if (v1N.Distance(v2N) < 2)
                        {
                            throw new InvalidDataException("For non-robust test d(v1, v2) must be >= 2.");
                        }



                        // building closure for v1 and v2
                        var vectorsInterval = v1N.Closure(v2N);

                        // building set of intersecting cubes of non-robust PDFs
                        var intersectedConjunctions = new CubeSet();

                        // checking wheather non-robust path test vectors interval not intersects with other conjunctions, corresponding to other non-robust paths
                        foreach (var pair2 in nonRobustPathsCopy)
                        {
                            var kForAnotherNonRobustPath = pair2.Value;

                            if (ReferenceEquals(kForAnotherNonRobustPath, kSet))
                            {
                                // skipping current set K
                                continue;
                            }

                            // iterating through conjunctions to check intersection
                            foreach (var kt in kForAnotherNonRobustPath)
                            {
                                // skipping conjunctions which contains current K set
                                if (kSet.Contains(kt))
                                {
                                    continue;
                                }

                                // building cube for conjunction
                                var ktCube = new Cube(circuit.Inputs.Count);
                                for (var i = 0; i < ktCube.Dimension; i++)
                                {
                                    ktCube[i] = BooleanValue.Undefined;
                                }

                                foreach (Vertex v in kt)
                                {
                                    var numberStr = v.Data as String;
                                    if (numberStr != null)
                                    {
                                        var number = Int32.Parse(numberStr);
                                        ktCube[number] = v.IsInverted ? BooleanValue.False : BooleanValue.True;
                                    }
                                }

                                if (!ktCube.IsOrthogonalTo(vectorsInterval))
                                {
                                    // memorizing intersected cube
                                    intersectedConjunctions.Add(ktCube);
                                }
                            }
                        }


                        // checking whether no one conjunction (for non-robust path) intersects with test vectors interval
                        if (intersectedConjunctions.Count == 0)
                        {
                            // nobody intersects with closure
                            // therefore test pair is relatively robust
                            var testNrr = new PdfTest {V1 = v1N, V2 = v2N, Data = fn, Data2 = fTree.GetPath(leaf)};
                            NonRobustRelativelyRobusTest.Add(testNrr);
                            break;
                        }
                        

                        // trying determing some bits of v1 and v2 to avoid intersection

                        // building equation to solve
                        var nonRobustD = new CubeSet();
                        var intersectedConjunctionsCount = 0; // number of conjunctions intersected with test pair interval
                        foreach (var cube in intersectedConjunctions.Select(intersectedCube => new Cube(intersectedCube)))
                        {
                            for (var i = 0; i < v1N.Dimension; i++)
                            {
                                // deleting variables from intersected conjunction on which v1 and v2 are orthogonal
                                if (v1N[i] != BooleanValue.Undefined && v2N[i] != BooleanValue.Undefined && v1N[i] != v2N[i])
                                {
                                    cube[i] = BooleanValue.Undefined;
                                }
                            }

                            // fixing variables in intersected conjunction which presented in v1 and v2
                            var cubeIsNull = false;
                            for (var i = 0; i < v1N.Dimension; i++)
                            {
                                if (v1N[i] != BooleanValue.Undefined &&
                                    (v2N[i] == v1N[i] || v2N[i] == BooleanValue.Undefined))
                                {
                                    // fixing variable determined by v1
                                    if (cube[i] == BooleanValue.Undefined)
                                    {
                                        // bit does not determined in intersected cube
                                        continue;
                                    }

                                    if (cube[i] == v1N[i])
                                    {
                                        // deleting variable
                                        cube[i] = BooleanValue.Undefined;
                                    }
                                    else
                                    {
                                        // deleting conjunction
                                        cubeIsNull = true;
                                        break;
                                    }
                                }
                                else if (v2N[i] != BooleanValue.Undefined &&
                                         (v1N[i] == v2N[i] || v1N[i] == BooleanValue.Undefined))
                                {
                                    // fixing variable determined by v2
                                    if (cube[i] == BooleanValue.Undefined)
                                    {
                                        // bit does not determined in intersected cube
                                        continue;
                                    }

                                    if (cube[i] == v2N[i])
                                    {
                                        // deleting variable
                                        cube[i] = BooleanValue.Undefined;
                                    }
                                    else
                                    {
                                        // deleting conjunction
                                        cubeIsNull = true;
                                        break;
                                    }
                                }
                            }

                            if (cube.Rank == 0)
                            {
                                // current conjunction could not be null, skipping it
                                intersectedConjunctionsCount++;
                                continue;
                            }

                            if (cubeIsNull)
                            {
                                // conjunction is null, skipping it
                                continue;
                            }

                            nonRobustD.Add(cube);
                        }


                        // trying solve the equation NonRobustD = 0 for all or for subset of IntersectedConjunctions

                        // building powerset of intersected conjunctions
                        var power = (int)Math.Pow(2, nonRobustD.Count);
                        var powerset = new List<CubeSet>();
                        for (var i = power - 1; i >= 0; i--)
                        {
                            var values = new[] { i };
                            var vector = new BooleanVector(values);

                            // building subset
                            var subset = new CubeSet();
                            var number = 0;
                            foreach (var cube in nonRobustD)
                            {
                                if (vector[number] == BooleanValue.True)
                                {
                                    subset.Add(cube);
                                }
                                number++;
                            }

                            powerset.Add(subset);
                        }



                        // solving equations
                        foreach (var equation in powerset)
                        {
                            // calculating the root of the equaion D = 0
                            var equationNullroot = equation.EquationNullRoot;
                            if (equationNullroot.Rank != 0)
                            {
                                // root found
                                v1N = v1N.Intersection(equationNullroot);
                                v2N = v2N.Intersection(equationNullroot);

                                // calculating number of intersected conjunctions
                                intersectedConjunctionsCount = intersectedConjunctionsCount + nonRobustD.Count - equation.Count;

                                break;
                            }

                            // could not solve the equation, trying to solve for another subset
                        }


                       


                        // building new PDF test
                        var testN = new PdfTest {V1 = v1N, V2 = v2N, Data = fn, Data2 = fTree.GetPath(leaf)};

                        if (intersectedConjunctionsCount == 0)
                        {
                            // relatively robust test pair, the best solution
                            NonRobustRelativelyRobusTest.Add(testN);
                            break;
                        }

                        // test candidate
                        testN.Data3 = intersectedConjunctionsCount;
                        testN.Data4 = intersectedConjunctions.Count;
                        testCandidates.Add(testN);

                        // building test pair for next conjunction in K
                    }


                    // searching for the best test candidate
                    var minIntersection = 32767;
                    PdfTest bestTest = null;
                    foreach (var test in testCandidates)
                    {
                        var measure = (int)test.Data3;
                        if (measure < minIntersection)
                        {
                            bestTest = test;
                            minIntersection = measure;
                        }
                    }

                    // adding best test to the result
                    if (!ReferenceEquals(bestTest, null))
                    {
                        NonRobustTest.Add(bestTest);
                    }
                }
            }


            
            // STEP 3.
            // searching for non-robust tests which are robustly testable
            /*
            foreach (var test in NonRobustTest)
            {

            }
             */
        }


        /// <summary>
        /// Builds compacted path-delay-fault test sequence.
        /// </summary>
        /// <param name="original">Original test vectors.</param>
        /// <param name="compacted">Compacted test vectors.</param>
        /// <returns>Compacted PDF Test sequence.</returns>
        static public List<Cube> BuildCompactedPdfTestSequence(BooleanSystem original, BooleanSystem compacted)
        {
            // making some simple verification tests

            // comparing number of outputs
            if (original.Functions.Count != compacted.Functions.Count)
            {
                throw new Exception("Tests are not compatible. Reason: different count of functions!");
            }

            // checking whether compacted system is realization of original
            /*
            if (!compacted.IsRealizationOf(original))
            {
                throw new Exception("Systems are not equivalent!");
            }
            */


            // building dictionary of all different compacted vectors and it's frequencies
            var vectorsMap = new Dictionary<Cube, List<PdfTest>>();
            foreach (var func in compacted.Functions)
            {
                foreach (var cube in func.Domain1)
                {
                    // checking whether cube has not been added already
                    var foundFlag = vectorsMap.Any(pair => pair.Key == cube);

                    if (foundFlag)
                    {
                        continue;
                    }

                    vectorsMap.Add(cube, new List<PdfTest>());
                }
            }


            // filling vectors map
            var pathNumber = 0;
            var secondPass = new Dictionary<List<KeyValuePair<Cube, List<PdfTest>>>, PdfTest>();
            foreach (var funcOrig in original.Functions)
            {
                Cube v1 = null;
                foreach (var cube in funcOrig.Domain1)
                {
                    if (v1 == null)
                    {
                        v1 = cube;
                        continue;
                    }

                    var v2 = cube;

                    // building PDF test
                    var test = new PdfTest { V1 = v1, V2 = v2, Data = pathNumber++, Data2 = original.Functions.IndexOf(funcOrig) };

                    // searching for compacted vectors for each vector of the pair
                    var v1Candidates = new List<KeyValuePair<Cube, List<PdfTest>>>();
                    var v2Candidates = new List<KeyValuePair<Cube, List<PdfTest>>>();
                    foreach (var pair in vectorsMap)
                    {
                        if (v1.Contains(pair.Key))
                        {
                            v1Candidates.Add(pair);
                        }
                        else if (v2.Contains(pair.Key))
                        {
                            v2Candidates.Add(pair);
                        }
                    }


                    if (v1Candidates.Count == 0 || v2Candidates.Count == 0)
                    {
                        // could not find any compatible compacted vector
                        throw new Exception("Could not find any compatible compacted vector.");
                    }

                    if (v1Candidates.Count == 1)
                    {
                        // one variant
                        v1Candidates[0].Value.Add(test);
                    }
                    else
                    {
                        // few variants

                        // memorizing variants for second pass
                        secondPass.Add(v1Candidates, test);
                    }

                    if (v2Candidates.Count == 1)
                    {
                        // one variant
                        v2Candidates[0].Value.Add(test);
                    }
                    else
                    {
                        // few variants

                        // memorizing variants for second pass
                        secondPass.Add(v2Candidates, test);
                    }


                    // reseting pair
                    v1 = null;
                }
            }


            // second pass
            foreach (var pair in secondPass)
            {
                var test = pair.Value;
                var candidates = pair.Key;

                // sorting candidates by using frequency in descending order
                var sortedCandidates = (from candidate in candidates orderby candidate.Value.Count descending select candidate);

                // using candidate with the largest frequency for building more bigger sets
                var selectedCandidate = sortedCandidates.First();
                selectedCandidate.Value.Add(test);
            }


            // sorting vectors map by frequency in descending order
            var sortedVectorsMap = (from pair in vectorsMap orderby pair.Value.Count descending select pair);

            // building test sequence
            var testSequence = new List<Cube>();
            while (sortedVectorsMap.First().Value.Count > 0)
            {
                var testsToDelete = new Dictionary<PdfTest, List<PdfTest>>();

                var firstPair = sortedVectorsMap.First();
                var compactedCube = firstPair.Key;
                var originalTests = firstPair.Value;

                // adding tests using the most frequent compactedCube
                var testVector1 = new Cube(compactedCube);
                var compactedSecondVectors = new List<Cube>();
                foreach (var originalTest in originalTests)
                {
                    testVector1 = new Cube(compactedCube) { Data = originalTest.Data, Data2 = new Cube(compactedCube), Data3 = originalTest.Data2 };

                    // searching for compacted second vector
                    Cube testVector2 = null;
                    var alreadyUsedFlag = false;
                    foreach (var mapPair in sortedVectorsMap)
                    {
                        if (mapPair.Key == firstPair.Key)
                        {
                            // skipping first pair
                            continue;
                        }

                        var tests = mapPair.Value;
                        if (!tests.Contains(originalTest)) continue;

                        // compacted second vector found

                        // memorizing test to delete
                        testsToDelete.Add(originalTest, tests);


                        // checking whether compacted second vector has been already used in this group
                        foreach (var usedVector in compactedSecondVectors)
                        {
                            if (usedVector != mapPair.Key) continue;

                            alreadyUsedFlag = true;

                            // updating path list
                            usedVector.Data = String.Format("{0}, {1}", usedVector.Data, testVector1.Data);

                            // updating functions list
                            usedVector.Data3 = String.Format("{0}, {1}", usedVector.Data3, testVector1.Data3);
                            break;
                        }


                        if (alreadyUsedFlag)
                        {
                            break;
                        }


                        testVector2 = new Cube(mapPair.Key);
                        compactedSecondVectors.Add(testVector2);

                        break;
                    }


                    if (alreadyUsedFlag)
                    {
                        continue;
                    }

                    if (testVector2 == null)
                    {
                        // could not find compacted second vector
                        throw new Exception("Could not find compacted second vector.");
                    }

                    testVector2.Data = testVector1.Data;
                    testVector2.Data2 = testVector1.Data2;
                    testVector2.Data3 = testVector1.Data3;

                    testSequence.Add(testVector1);
                    testSequence.Add(testVector2);
                }


                testSequence.Add(testVector1);


                // updating map

                // clearing tests for first cube
                firstPair.Value.Clear();

                // deleting used tests
                foreach (var testToDeletePair in testsToDelete)
                {
                    var test = testToDeletePair.Key;
                    var tests = testToDeletePair.Value;
                    tests.Remove(test);
                }


                // resorting vectors map
                var resortedVectorsMap = (from pair in sortedVectorsMap orderby pair.Value.Count descending select pair);
                sortedVectorsMap = resortedVectorsMap;
            }

            return testSequence;
        }
    }
}
